篇首语:本文由编程笔记#小编为大家整理,主要介绍了RecyclerView添加Header的正确方式相关的知识,希望对你有一定的参考价值。
看了一下博客目录,已经有好几篇博客是关于RecyclerView
的,不过对于这么一款强大的控件,我还是要再写一篇博客来学习一下,这篇博客的主题是《为RecyclerView添加header》,当然在看完这篇博客后,相信添加Footer你也应该能够学会。话说在这么多新控件中为何RecyclerView
备受开发者的喜爱?这还是因为在android发展到今天基本上还没有像RecyclerView
这么灵活的一个玩意,鉴于他的灵活以及强大,很多人(包括我)已经开始抛弃ListView
和GridView
转为RecyclerView
了,再使用过RecyclerView
和被善变的需求折磨后,我相信会有越来越多的人转到RecyclerView
的使用上。
好了,废话不多说了,这篇博客我们要解决的问题有:
- 如何为RecyclerView添加Header
- 如何让Header适配各种LayoutManager
- 在有Header的情况下,我们的分割线该怎么画
- 作为一个懒惰的程序员,如何将这些做到最简便
大家在使用ListView
的时候可以很轻松的添加headers, 但是不知道大家发现没有,RecyclerView
和各种LayoutManager
都没有哪个方法是为添加header而设立的,这个时候我们就开始思考如何为RecyclerView
添加header了。 这里我们的解决方案和网上你能搜到的大多数方案一样,是通过控制Adapter
的itemType
来设置的,思路就是根据不同的itemType去加载不同的布局。
/**
* Created by qibin on 2015/11/5.
*/
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public static final int TYPE_HEADER &#61; 0;
public static final int TYPE_NORMAL &#61; 1;
private ArrayList
private View mHeaderView;
private OnItemClickListener mListener;
public void setOnItemClickListener(OnItemClickListener li)
mListener &#61; li;
public void setHeaderView(View headerView)
mHeaderView &#61; headerView;
notifyItemInserted(0);
public View getHeaderView()
return mHeaderView;
public void addDatas(ArrayList
mDatas.addAll(datas);
notifyDataSetChanged();
&#64;Override
public int getItemViewType(int position)
if(mHeaderView &#61;&#61; null) return TYPE_NORMAL;
if(position &#61;&#61; 0) return TYPE_HEADER;
return TYPE_NORMAL;
&#64;Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
if(mHeaderView !&#61; null && viewType &#61;&#61; TYPE_HEADER) return new Holder(mHeaderView);
View layout &#61; LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new Holder(layout);
&#64;Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
if(getItemViewType(position) &#61;&#61; TYPE_HEADER) return;
final int pos &#61; getRealPosition(viewHolder);
final String data &#61; mDatas.get(pos);
if(viewHolder instanceof Holder)
((Holder) viewHolder).text.setText(data);
if(mListener &#61;&#61; null) return;
viewHolder.itemView.setOnClickListener(new View.OnClickListener()
&#64;Override
public void onClick(View v)
mListener.onItemClick(pos, data);
);
public int getRealPosition(RecyclerView.ViewHolder holder)
int position &#61; holder.getLayoutPosition();
return mHeaderView &#61;&#61; null ? position : position - 1;
&#64;Override
public int getItemCount()
return mHeaderView &#61;&#61; null ? mDatas.size() : mDatas.size() &#43; 1;
class Holder extends RecyclerView.ViewHolder
TextView text;
public Holder(View itemView)
super(itemView);
if(itemView &#61;&#61; mHeaderView) return;
text &#61; (TextView) itemView.findViewById(R.id.text);
interface OnItemClickListener
void onItemClick(int position, String data);
这里我们重写了getItemViewType
方法&#xff0c;并根据位置来返回不同的type&#xff0c;这个type是我们预先商定好的常量&#xff0c;接在onCreateViewHolder
方法中来判断itemType&#xff0c;如果是header&#xff0c;则返回我们设置的headerView&#xff0c;否则正常加载item布局&#xff0c;相信大家对于上面的代码不会有任何疑问&#xff0c;接下来我们就在Activity中用一下试试看&#xff0c;
&#64;Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView &#61; (RecyclerView) findViewById(R.id.list);
mLayoutManager &#61; new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter &#61; new MyAdapter();
mRecyclerView.setAdapter(mAdapter);
mAdapter.addDatas(generateData());
setHeader(mRecyclerView);
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener()
&#64;Override
public void onItemClick(int position, String data)
Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
);
private void setHeader(RecyclerView view)
View header &#61; LayoutInflater.from(this).inflate(R.layout.header, view, false);
mAdapter.setHeaderView(header);
这里LayoutManager
我们使用了LinearLayoutManager
&#xff0c;并且给Adapter
设置了一个header&#xff0c;运行一下
看看效果&#xff1a;
恩&#xff0c;还不错&#xff0c;item的点击事件也很完美&#xff0c;那接下来&#xff0c;我们将LayoutManager
换成GridLayoutManager
看看咋样。
// mLayoutManager &#61; new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mLayoutManager &#61; new GridLayoutManager(this, 2);
哎哟&#xff0c;我的小心脏啊&#xff0c;快受不了了&#xff0c;这是什么玩意&#xff0c;我们的header竟然作为一个cell出现在了界面上&#xff0c;这完全不是我们想要的效果啊&#xff01; 冷静下来想想&#xff0c;肯定会有解决方法的吧。这时候我们就该引入一个不太常用的方法了&#xff1a;
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
&#64;Override
public int getSpanSize(int position)
return getItemViewType(position) &#61;&#61; TYPE_HEADER
? gridManager.getSpanCount() : 1;
);
我们解释一下这段代码&#xff0c;首先我们设置了一个SpanSizeLookup
&#xff0c;这个类是一个抽象类&#xff0c;而且仅有一个抽象方法getSpanSize
&#xff0c;这个方法的返回值决定了我们每个position上的item占据的单元格个数&#xff0c;而我们这段代码综合上面为GridLayoutManager
设置的每行的个数来解释的话&#xff0c;
就是当前位置是header的位置&#xff0c;那么该item占据2个单元格&#xff0c;正常情况下占据1个单元格。那这段代码放哪呢&#xff1f; 为了以后的封装&#xff0c;我们还是在Adapter
中找方法放吧。
我们在Adapter
中再重写一个方法onAttachedToRecyclerView
&#xff0c;
&#64;Override
public void onAttachedToRecyclerView(RecyclerView recyclerView)
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager &#61; recyclerView.getLayoutManager();
if(manager instanceof GridLayoutManager)
final GridLayoutManager gridManager &#61; ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
&#64;Override
public int getSpanSize(int position)
return getItemViewType(position) &#61;&#61; TYPE_HEADER
? gridManager.getSpanCount() : 1;
);
这个时候我们再来看一下效果&#xff0c;
恩&#xff0c;这次达到我们的要求了&#xff0c;不过对于StaggeredGridLayoutManager
我们还没做处理&#xff0c;而且我们还发现StaggeredGridLayoutManager
中并没有像GridLayoutManager
中这样的方法&#xff0c;我们还需要单独为StaggeredGridLayoutManager
单独处理一下。
我们继续重写Adapter
中另外一个方法。
&#64;Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp &#61; holder.itemView.getLayoutParams();
if(lp !&#61; null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams)
StaggeredGridLayoutManager.LayoutParams p &#61; (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(holder.getLayoutPosition() &#61;&#61; 0);
这里的处理方式是用通过LayoutParams
&#xff0c;而且这里更简单&#xff0c;StaggeredGridLayoutManager.LayoutParams
为我们提供了一个setFullSpan
方法来设置占领全部空间&#xff0c;好开心&#xff0c;看一下StaggeredGridLayoutManager
的效果&#xff0c;
啊&#xff0c; 怎么和上面的效果一样&#xff1f; 很简单嘛&#xff0c;我们的item都是等高的。
这是我们开开心心的继续写代码&#xff0c;并且为我们的item添加了分隔符&#xff0c;分隔符我还是用的翔哥写的那个&#xff0c;毕竟翔哥写的太好了&#xff0c;而且我们没有必要重复造轮子&#xff0c;不过这时候问题出现了&#xff0c;相信你也肯定能猜到应该会出现问题了&#xff0c;因为不管我们怎么处理&#xff0c;header对于RecyclerView
来说还是一个普普通通的item&#xff0c;这时候我们添加分割线&#xff0c;肯定也会对header产生影响&#xff0c;那下面&#xff0c;我们再来对翔哥的分割线改造一下吧。
public class GridItemDecoration extends RecyclerView.ItemDecoration
private static final int[] ATTRS &#61; new int[]android.R.attr.listDivider;
private Drawable mDivider;
private boolean hasHeader;
public GridItemDecoration(Context context)
final TypedArray a &#61; context.obtainStyledAttributes(ATTRS);
mDivider &#61; a.getDrawable(0);
a.recycle();
public GridItemDecoration(Context context, boolean header)
this(context);
hasHeader &#61; header;
...
&#64;Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state)
int position &#61; parent.getChildAdapterPosition(view);
int spanCount &#61; getSpanCount(parent);
int childCount &#61; parent.getAdapter().getItemCount();
int pos &#61; position;
if(hasHeader)
if(position &#61;&#61; 0)
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
return;
else
pos &#61; position - 1;
if (isLastColum(parent, pos, spanCount, childCount))
outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
else
outRect.set(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight());
改造的地方是获取偏移量的方法我们换了一个&#xff0c;因为原来的那个已经过时了&#xff0c;而且&#xff0c;这里我们还加了一个boolean类型的hasHeader
变量来表示是不是有header&#xff0c;如果hasHeader并且position为0&#xff0c;那么我们仅仅绘制底部的分割线&#xff0c;其他的地方不绘制&#xff0c;在有header的情况下&#xff0c;我们还需要将position减1&#xff0c;因为我们认为的第1个item其实是第2个。这个时候我们再来看看有分割线的效果。
看来我们的想法是对的&#xff0c;header部分除了底部有一个分割线外&#xff0c;并没有其他的分割线&#xff0c;这也完全符合我们的需求。
这下好了&#xff0c;基本上完美的处理好了&#xff0c;可是难道我们对于不同的Adapter
都需要写那么多代码吗&#xff1f; 对于一个懒程序员来说&#xff0c;这肯定是一个可怕的事情&#xff0c;所以&#xff0c;我们还需要对我们的Adapter
进行封装&#xff0c;目的就是可以轻轻松松的写代码&#xff0c;
/**
* Created by qibin on 2015/11/5.
*/
public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public static final int TYPE_HEADER &#61; 0;
public static final int TYPE_NORMAL &#61; 1;
private ArrayList
private View mHeaderView;
private OnItemClickListener mListener;
public void setOnItemClickListener(OnItemClickListener li)
mListener &#61; li;
public void setHeaderView(View headerView)
mHeaderView &#61; headerView;
notifyItemInserted(0);
public View getHeaderView()
return mHeaderView;
public void addDatas(ArrayList
mDatas.addAll(datas);
notifyDataSetChanged();
&#64;Override
public int getItemViewType(int position)
if(mHeaderView &#61;&#61; null) return TYPE_NORMAL;
if(position &#61;&#61; 0) return TYPE_HEADER;
return TYPE_NORMAL;
&#64;Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, final int viewType)
if(mHeaderView !&#61; null && viewType &#61;&#61; TYPE_HEADER) return new Holder(mHeaderView);
return onCreate(parent, viewType);
&#64;Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
if(getItemViewType(position) &#61;&#61; TYPE_HEADER) return;
final int pos &#61; getRealPosition(viewHolder);
final T data &#61; mDatas.get(pos);
onBind(viewHolder, pos, data);
if(mListener !&#61; null)
viewHolder.itemView.setOnClickListener(new View.OnClickListener()
&#64;Override
public void onClick(View v)
mListener.onItemClick(pos, data);
);
&#64;Override
public void onAttachedToRecyclerView(RecyclerView recyclerView)
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager &#61; recyclerView.getLayoutManager();
if(manager instanceof GridLayoutManager)
final GridLayoutManager gridManager &#61; ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
&#64;Override
public int getSpanSize(int position)
return getItemViewType(position) &#61;&#61; TYPE_HEADER
? gridManager.getSpanCount() : 1;
);
&#64;Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp &#61; holder.itemView.getLayoutParams();
if(lp !&#61; null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams)
StaggeredGridLayoutManager.LayoutParams p &#61; (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(holder.getLayoutPosition() &#61;&#61; 0);
public int getRealPosition(RecyclerView.ViewHolder holder)
int position &#61; holder.getLayoutPosition();
return mHeaderView &#61;&#61; null ? position : position - 1;
&#64;Override
public int getItemCount()
return mHeaderView &#61;&#61; null ? mDatas.size() : mDatas.size() &#43; 1;
public abstract RecyclerView.ViewHolder onCreate(ViewGroup parent, final int viewType);
public abstract void onBind(RecyclerView.ViewHolder viewHolder, int RealPosition, T data);
public class Holder extends RecyclerView.ViewHolder
public Holder(View itemView)
super(itemView);
public interface OnItemClickListener<T>
void onItemClick(int position, T data);
我们将BaseRecyclerAdapter
抽象起来&#xff0c;并且提供两个抽象方法onCreate
和onBind
用来创建holder和绑定数据&#xff0c;而对于header做的一系列工作&#xff0c;我们都放到了BaseRecyclerAdapter
中&#xff0c;而继承BaseRecyclerAdapter
后&#xff0c;我们仅仅关心我们的holder怎么创建和数据怎么绑定就ok。例如下面代码&#xff1a;
/**
* Created by qibin on 2015/11/7.
*/
public class MyAdapter extends BaseRecyclerAdapter<String>
&#64;Override
public RecyclerView.ViewHolder onCreate(ViewGroup parent, int viewType)
View layout &#61; LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new MyHolder(layout);
&#64;Override
public void onBind(RecyclerView.ViewHolder viewHolder, int RealPosition, String data)
if(viewHolder instanceof MyHolder)
((MyHolder) viewHolder).text.setText(data);
class MyHolder extends BaseRecyclerAdapter.Holder
TextView text;
public MyHolder(View itemView)
super(itemView);
text &#61; (TextView) itemView.findViewById(R.id.text);
这样我们再用起来就简单多了&#xff0c;对于这样的封装&#xff0c;我们还算满意&#xff0c;再做完添加header后&#xff0c;相信大家对于footer也有想法了&#xff0c;有想法就实现它吧&#xff0c;扩展一下BaseRecyclerAdapter
就ok啦。
好了&#xff0c;这篇博客就到这里吧&#xff0c;最后是本文代码的下载。
代码下载&#xff0c;戳这里